/*
* SISTEMAS OPERATIVOS II - TP1: SOCKETS (Client)
* Alumno: Cipolatti Mauro
* Matrícula: 35990189
* 
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <netdb.h>
#include <unistd.h>
#include <termios.h>
#include <sys/stat.h>
#include <time.h>                                              

#define TAM 256        
char tomar();															//Función utilizada para tomar de a una tecla.
                     
int main( int argc, char *argv[] ) {
       
        char buffer[TAM];                                               //Buffer por donde se da la comunicación.
        char temp[TAM];													//Buffer utilizado para trabajar y no perder el original.
        char *segundoarg=temp;											
        char *parseado;
        char bufflog[TAM];
        int puerto = 6211;
        int puertoUDP= 6210;
        int n=0, size, recibidos,enviados,leidos,flagerr=0;             //n para el control de errores.
       
        int pid=fork();                                                 //Apenas inicia, creamos el hijo que es el logger.
if(pid < 0){perror("ERROR en fork");exit(1);}
        if(pid == 0)                                                    //Proceso Hijo. Server del logger.
        {
          char escribe[TAM];	                                        //Lo que se va a escribir en el archivo log
          int socklogfd = socket( AF_UNIX, SOCK_STREAM, 0);             //Creamos el socket del log.
          if(socklogfd<0){perror("ERROR abrierndo el socket UNIX");exit(0);}      
          unlink ( "logger" );                                          //Borramos el nombre de archivo si ya existe.

          //Ligamos el socket a una dirección.
          struct sockaddr_un log_addr;
          memset( &log_addr, 0, sizeof(log_addr) );                     //Limpiamos el struct
          log_addr.sun_family = AF_UNIX;                                //Definimos familia
          strcpy( log_addr.sun_path, "logger" );                        //Se va a comunicar a traves del archivo logger.
          int loglen = strlen(log_addr.sun_path) + sizeof(log_addr.sun_family);
          bind( socklogfd,(struct sockaddr *)&log_addr,loglen );        //Ligadura
          if( n<0 ) {perror( "ERROR en ligadura LOG" );exit(1);}

          listen( socklogfd, 1 );                                       //Escuchamos por una conexion.
          struct sockaddr_un clilog_addr;
          uint cliloglen = sizeof( clilog_addr );
          int newsocklogfd = accept( socklogfd, (struct sockaddr *) &clilog_addr, &cliloglen ); //Aceptamos conecciones entrantes.Se frena el proceso.
          if (newsocklogfd < 0){perror("ERROR en accept LOG");exit(1);}
          close( socklogfd );
         
          time_t t;                                                     //Variables para obtener fecha y hora
          struct tm * now;
          FILE *logf = fopen("comunicacion.log","w");
          while(1)
          {
           memset( bufflog, 0, TAM );
           n = read( newsocklogfd, bufflog, TAM-1 );					//Esperamos a recibir algo en el socket.
           if ( n < 0 ) {perror( "ERROR en lectura de socket LOG" );exit(1);}
           t = time(0);                                              	//Una vez que recibimos, tomamos la hora.
           now = localtime( & t );
           strftime(escribe, sizeof(escribe), "%Y-%m-%d.%X", now);		//La copiamos en escribe
           strcat(escribe," / ");                                		//Concatenamos una / y el mensaje que llego en bufflog
           strcat(escribe,bufflog);
           strcat(escribe,"\n");
           fprintf(logf,"%s",escribe);                           		//Escribimos en el archivo
                 
           if(!strcmp("bye",bufflog)){fclose(logf);remove("logger");break;}//Control de corte.
          }
          exit(0);
        }																      
else{																	//Proceso padre.
    if (argc != 2) {perror("ERROR. Indicar Hostname\n");exit(1);}		//Control de utilizacion. Se debe ingresar el hostname.
    printf("Conectando...\n");
    sleep(1);                                                           //Dormimos al padre por 1seg para darle tiempo al hijo de crear el socket del logger.  
    int socklogclifd = socket(AF_UNIX, SOCK_STREAM, 0);                 //Creamos socket UNIX del logger en el padre.
    if(socklogclifd < 0){perror("ERROR abriendo el socket cli LOG");exit(1);}
    struct sockaddr_un servlog_addr;                                    //Configuración del socket UNIX.       
    memset( (char *)&servlog_addr, '\0', sizeof(servlog_addr) );
    servlog_addr.sun_family = AF_UNIX;
    strcpy( servlog_addr.sun_path, "logger" );
    int servlen = strlen( servlog_addr.sun_path) + sizeof(servlog_addr.sun_family);
    n= connect( socklogclifd, (struct sockaddr *)&servlog_addr, servlen );
    if(n<0){perror("ERROR en connect LOG");exit(1);}
   
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);                       //Creamos el nuevo socket TCP.
    if (sockfd < 0)
    {perror("ERROR abriendo el socket TCP");exit(1);}
    int sockfdUDP = socket( AF_INET, SOCK_DGRAM, 0 );                   //Creamos el nuevo socket UDP.
    if (sockfdUDP < 0)
    {perror("ERROR abriendo el socket UDP");exit(1);}
   
    struct hostent *server;
    struct hostent *serverUDP;
    server= gethostbyname(argv[1]);                                     //Buscamos el host por su nombre. Si devuelve NULL, no se encontro.
    if(server == NULL)
    { perror("ERROR. No se encontro el servidor");exit(1);}
    serverUDP=server;
   
    struct sockaddr_in serv_addr;                                       //Configuracion del socket TCP
    memset((char *) &serv_addr, 0,sizeof(serv_addr));                                  
    serv_addr.sin_family = AF_INET;
    bcopy((char *)server->h_addr,(char *)&serv_addr.sin_addr.s_addr,server->h_length);
    serv_addr.sin_port = htons(puerto);
    if (connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0)
    {perror("ERROR en la conexion."); exit(1);}

    struct sockaddr_in dest_addrUDP;                                    //Configuracion del socket UDP
    dest_addrUDP.sin_family = AF_INET;
    dest_addrUDP.sin_port = htons( puertoUDP );
    dest_addrUDP.sin_addr = *( (struct in_addr *)serverUDP->h_addr );
    memset( &(dest_addrUDP.sin_zero), '\0', 8 );
    int tamano_direccion = sizeof( dest_addrUDP );

    int i,flagent=0;                                                 	//Recursos para el tecla x tecla.
    char caracter;

while(1){																//While infinito del cliente. Corta cuando recibe "bye".
    
    memset(buffer,0,TAM);												//Reseteamos los buffers.
    memset(temp,0,TAM);
    setvbuf(stdout, NULL, _IONBF, 0);									//Cambiamos el stdout para que se almacene en un buffer.
																		//Vamos a iniciar con el tecla por tecla.
    i=0;																//Para conocer la posición de la cadena.
    while(flagent==0)													//Tenemos una bandera para ver cuando el usuario presiona enter.
    {printf("Ingrese mensaje: ");                                    	
	 caracter =tomar();                                                 //Tomamos la primer tecla.	
    while(caracter!='\n')                                               //Mientras la tecla es distinta de enter, entramos al bucle.
    {flagent=1; 														//No se presionó enter. Se va a ingresar un comando. Levantamos la bandera para no volver a entrar.
         if(caracter == 127)                                            //Si caracter es 'backspace' tenemos que borrar el ultimo.
         {
          if(i!=0)                                                      //Si i!=0 borramos uno. Sino no, porque estamos al principio.
          {
           buffer[strlen(buffer)-1] = '\0';                             //Ponemos el enter en el lugar anterior al ultimo.  
           printf("\b \b");                                             //Borramos un caracter de la pantalla.
           i--;                                                         //Restamos al tamaño (control).
		   n = write(sockfd,"borra1;",7);                     			//Mandamos 'borra1;' para que el server borre el ultimo
		   if (n < 0){perror("ERROR escribiendo al socket (client)");exit(1);}
           }
         }
         else
         {
          buffer[i]= caracter;											//No habia que borrar. Agregamos el caracter al final.
          printf("%c",caracter);							
          i++;															//Imprimimos y avanzamos un lugar
          n = write(sockfd,(void *)&caracter,1);                     	//Mandamos el caracter
		  if (n < 0){perror("ERROR escribiendo al socket (client)");exit(1);}
         }
        caracter =tomar();												//Tomamos una nueva tecla antes de volver a arrancar.
    }
    printf("\n");														//Presionó enter. Lo imprimimos en pantalla. Si la bandera de "flagent" no se levanto, se ingresó un enter sin ningun comando. No salimos del bucle.
	}
      flagent=0;  														//Reseteamos la bandera de enter.
       
        n = write(sockfd,"msgend",6);                     				//Mandamos 'msgend' para indicar el final del comando.
		if (n < 0){perror("ERROR escribiendo al socket (client)");exit(1);}
  
    strcpy(temp,buffer);												//Copiamos el comando en temp para trabajar y no perder el original.
 
    if( !strcmp( "bye", temp ) )                                        //Chequeamos si es bye.
        {
         printf("Fin de la comunicacion.\n");
         write( socklogclifd, buffer, strlen(buffer) );                 //Comunicamos al logger
         exit(0);
        }
    else{
        size=0;                                                         //size se utiliza para control de cuando cortar el envio/recepcion. Es el tam del archivo.
 
        strcpy(temp,buffer);
        if(!strcmp( "get", strtok(temp," ")))                           //Chequeamos si es get.
        {      
           segundoarg=temp;                                             //Vamos a parsear para poder hacer get de cualquier archivo.
           while(segundoarg != NULL)
           {
           parseado=segundoarg;  
           segundoarg = strtok (NULL, "/");                                                    
           }
           
           FILE *FilePointer=fopen(parseado,"w+b");                     //Lo abrimos en modo escritura binaria.
           if(FilePointer==NULL){perror("ERROR abriendo archivo GET");exit(1);}
           
           memset(buffer,0,TAM);
           n= sendto( sockfdUDP, (void *)buffer, TAM-1, 0, (struct sockaddr *)&dest_addrUDP, tamano_direccion ); //Hacemos una primera conexion con el servidor con el buffer en blanco                
           if ( n < 0 ) {perror( "Escritura en socket" );exit(1);}
           n= recvfrom( sockfdUDP, (void *)buffer, TAM-1, 0, (struct sockaddr *)&dest_addrUDP, (socklen_t*)&tamano_direccion );//Escuchamos. El servidor nos pasa el tamaño del archivo.
           if ( n < 0 ) {perror( "Lectura en socket" );exit(1);}
           
           if(!strcmp( "ERROR", strtok(buffer," ")))                    //Chequeamos si el servidor nos devolvio error en el archivo o no.
           {printf("ERROR en el archivo requerido.\n");
           strcat(buffer," en el archivo '");strcat(buffer,parseado);strcat(buffer,"' (get)");//Creamos mensaje a enviar al logger.
           remove(parseado);                                            //Borramos el archivo abierto para recibir.
           n=write( socklogclifd, buffer, strlen(buffer) );				//Informamos al logger.
           if(n<0){perror("ERROR escribiendo en el socket LOG");exit(1);}
           read(sockfd,buffer,TAM-1);                                   //Flush
           }
           else{
           size=atoi(buffer);                                           //Si no hubo error, tomamos el tam del archivo para controlar cuando dejar de recibir.
           recibidos=0;
       
        while(recibidos!=size){
           memset(buffer,0,TAM);
           n = recvfrom( sockfdUDP, (void *)buffer, TAM, 0, (struct sockaddr *)&dest_addrUDP, (socklen_t*)&tamano_direccion );//Escuchamos hasta 256 bytes y tomamos cuantos nos llegan en "n".
           if(n<0){perror("ERROR en lectura de socket UDP");exit(1);}
           
           fwrite(buffer,1,n,FilePointer);                              //Escribimos la misma cantidad de bytes que recibimos.
           if(n<0){perror("ERROR escribiendo el archivo");exit(1);}
           recibidos = recibidos+n;  									//Vamos acumulando los bytes para saber cuando cortar.
           }
           
           if(fclose(FilePointer)!=0){perror("ERROR cerrando el archivo");exit(1);} //Una vez recibido, cerramos el archivo.
          printf("Archivo '%s' recibido con exito.\n",parseado);		//Informamos al usuario.
          memset(buffer,0,TAM);strcat(buffer,"Archivo '");strcat(buffer,parseado);strcat(buffer,"' recibido con exito (get).");
          n=write( socklogclifd, buffer, strlen(buffer) );				//Informamos al logger.
          if(n<0){perror("ERROR escribiendo en el socket LOG");exit(1);}
          read(sockfd,buffer,TAM-1);									//Flush.
                }
        }
        else{
        strcpy(temp,buffer);
        if(!strcmp( "put", strtok(temp," ")))                           //Chequeamos si es put
        {
               
           segundoarg = strtok (NULL, " ");                             //Obtenemos la ruta o nombre del archivo
         
           FILE *FilePointer=fopen(segundoarg,"rb");                    //Lo abrimos en modo binario.
           if(FilePointer!=NULL){										//Si se pudo abrir el archivo, continuamos normalmente. Sino pasamos a informar el error al usuario y al server.
																		
           struct stat st;                                              //No hubo error. Buscamos el tamaño del archivo.
           stat(segundoarg, &st);
           size = st.st_size;
           rewind(FilePointer);
                       
           sprintf(buffer,"%d",size);									//Copiamos el tamaño al buffer.
           n = sendto( sockfdUDP, buffer, TAM-1, 0, (struct sockaddr *)&dest_addrUDP, tamano_direccion );//Enviamos el tamaño.
           if(n<0){perror("ERROR en lectura de socket UDP");exit(1);}                         
                               
           enviados=0,leidos=0;
           while(enviados!=size){
           memset( buffer, 0, TAM );                                    
           leidos=fread(buffer,1,TAM,FilePointer);                      //Leemos el archivo y guardamos la cant. de bytes leidos.
           n=sendto( sockfdUDP,(void *) buffer, leidos, 0, (struct sockaddr *)&dest_addrUDP,  tamano_direccion );//Se envian los bytes.
           if(n<0){perror("ERROR en escritura de socket UDP");exit(1);}
           enviados=enviados+leidos;                                    //Vamos acumulando los bytes enviados para saber cuando cortar.
           }
                       
           if(fclose(FilePointer)!=0){perror("ERROR cerrando el archivo");exit(1);}//Una vez enviado, cerramos el archivo.
           printf("Archivo '%s' enviado con exito\n",segundoarg);
           memset(buffer,0,TAM);
           strcat(buffer,"Archivo'");strcat(buffer,segundoarg);strcat(buffer,"' enviado con exito (put).");
           n=write( socklogclifd, buffer, strlen(buffer) );             //Mandamos el mensaje al logger.
           if(n<0){perror("ERROR escribiendo en el socket LOG");exit(1);}
           read(sockfd,buffer,TAM-1);                                   //flush.
                }
           else               		                                    //Hubo error abriendo el archivo del put.Mandamos ERROR al server y lo grabamos en el logger.
           {
            perror("ERROR abriendo archivo PUT");          
            memset(buffer,0,TAM);n=1;
            strcat(buffer,"ERROR abriendo el archivo'");strcat(buffer,segundoarg);strcat(buffer,"' (put)");
            n=write( socklogclifd, buffer, strlen(buffer) );           //Mandamos el mensaje al logger.
            if(n<0){perror("ERROR escribiendo en el socket LOG");exit(1);}
            memset(buffer,0,TAM);                                                                          
            strcat(buffer,"ERROR");
            n = sendto( sockfdUDP, buffer, TAM-1, 0, (struct sockaddr *)&dest_addrUDP, tamano_direccion );//Enviamos ERROR al server.
            read(sockfd,buffer,TAM-1);                             		//flush
           }
        }
    else //no era ni bye ni put ni get. Es un comando cualquiera.
    {strcpy(temp,buffer);                                     		     //Guardamos el comando en temp para ponerlo en el logger.
     while(1){  
     memset(buffer,0,TAM);
     n = read(sockfd,buffer,TAM-1);                                     //Vamos a leer lo que venga del server hasta que mande "Recepcion OK".
     if (n < 0){perror("ERROR reading from socket TCP");exit(1);}
   
    if(strcmp( "Recepción OK", buffer)==0)								//Si mandó "Recepción OK" hay que terminar.
    {
     if(flagerr==1)														//flagerr se utiliza para saber si hubo error en la ejecución del comando para informar al logger.
     {
     memset(buffer,0,TAM);
     strcat(buffer,"ERROR en la ejecucion de '");strcat(buffer,temp);strcat(buffer,"'.");
     write( socklogclifd, buffer, strlen(buffer) );  					//Informamos al buffer del error.
      }
     else{																//No hubo error. Informamos ejecución exitosa.
     memset(buffer,0,TAM);
     if(!strcmp( "", strtok(temp,"\n"))){ memset(temp,0,TAM);strcpy(temp,"/n");}
     strcat(buffer,"Ejecucion exitosa: '");strcat(buffer,temp);strcat(buffer,"'.");
     write( socklogclifd, buffer, strlen(buffer) );						//Mandamos msj al logger
     }
    break;																//Dejamos de escuchar.
    }	
    else{printf("%s",buffer);											//Todavia hay que seguir recibiendo información. Imprimimos lo que llega del server.
     if(!strcmp( "ERROR", strtok(buffer," ")))							//Buscamos si hay error para levantar la bandera para luego informar al logger.
         {flagerr=1;}
         else{flagerr=0;}
    }}}}
    memset(buffer,0,TAM);}//fin else
    memset(buffer,0,TAM);}//fin while grande
}
return 0;
}

char tomar()															//Función tomar para ir tomando de a una tecla por vez.
{
        char buf = 0;
        struct termios old = {0};
        if (tcgetattr(0, &old) < 0)
 perror("tcsetattr()");
        old.c_lflag &= ~ICANON;
        old.c_lflag &= ~ECHO;
        old.c_cc[VMIN] = 1;
        old.c_cc[VTIME] = 0;
        if (tcsetattr(0, TCSANOW, &old) < 0)
 perror("tcsetattr ICANON");
        if (read(0, &buf, 1) < 0)
 perror ("read()");
        old.c_lflag |= ICANON;
        old.c_lflag |= ECHO;
        if (tcsetattr(0, TCSADRAIN, &old) < 0)
 perror ("tcsetattr ~ICANON");
        return (buf);
}


